#include "Renderer.h"
#include "SceneGrpah.h"
#include "RenderQueue.h"
#include "GraphicsDriver.h"
#include "ShaderProgramManager.h"
#include "GeometryDataManager.h"
#include "MaterialManager.h"

namespace Nezha
{

	Renderer* Renderer::msGlobalRenderer = NULL;

	Renderer* Renderer::GetGlobal()
	{
		return msGlobalRenderer;
	}

	void Renderer::SetGlobal(Renderer* r)
	{
		if(r && !msGlobalRenderer)
		{
			msGlobalRenderer = r;
		}
		else if(!r)
		{
			msGlobalRenderer = NULL;
		}
		else
		{
			NZ_ASSERT(false);
		}
	}

	Renderer::Renderer(const _string& name)
		:mName(name)
		,mColorBit(NEZHA_DEFAULT_COLOR_DEPTH)
		,mWidth(NEZHA_DEFAULT_SCREEN_WIDTH)
		,mHeight(NEZHA_DEFAULT_SCREEN_HEIGHT)
		,mFrameDelta(0)
		,mSceneGraph(NULL)
		,mLastRenderQueueEntry(NULL)
		,mCurrentRenderQueueEntry(NULL)
		,mDefBlendStateOverride(true)
		,mDefCullOrderOverride(true)
		,mDefDepthOverride(true)
		,mDefFillModeOverride(true)
		,mDefOffsetOverride(true)
		,mDefStencilOverride(true)
		,mDefColorMaskOverride(true)
		,mDriver(NULL)
		,mGeometryDataManager(NULL)
		,mShaderProgramManager(NULL)
		,mFramesPerSecond(0)
		,mDrawCalls(0)
		,mTriangleCount(0)
		,mCurrentViewport(NULL)
		,mCurrentCam(NULL)
		,mFrameBuffer(NULL)
		,mCurrentShader(NULL)
	{
		memset(mBuiltinParams, 0, sizeof(mBuiltinParams));
		setDefaultCullOrder(RSCO_CCW);
		setDefaultFillMode(RSFM_SOLID);
	}

	Renderer::~Renderer()
	{
		for(u32 i = 0; i < MAX_RPT_NUM; i++)
		{
			NZ_Delete mBuiltinParams[i];
		}
	}

	void Renderer::setRenderMaterial(MaterialPtr& mat)
	{
		if(mCurrentMaterial == mat)
		{
			// do nothing if no material switch.
			return;
		}

		mCurrentMaterial = mat;
		// TODO
	}

	void Renderer::setCamera(Camera* cam)
	{
		mFrameBuffer->setCamera(cam);
	}

	Camera* Renderer::getCamera()
	{
		return mFrameBuffer->getCamera();
	}

	void Renderer::setViewport(Viewport *vp)
	{
		mCurrentViewport = vp;
	}

	void Renderer::setShader(ShaderProgram* sp)
	{
		if(mCurrentShader == sp)
		{
			return;
		}

		setShaderImpl(sp);
		mCurrentShader = sp;
	}

	void Renderer::setGeometry(GeometryDataBatch* gd, GeometryDataBatch* prevGd)
	{
		if(gd == prevGd)
		{
			// with different material or non-batched gds?
			return;
		}

		setGeometryImpl(gd, prevGd);
	}

	//RenderTarget* Renderer::createRenderTarget(u32 numTargets, TexFormat tf, u32 w, u32 h, int priority, bool autoMips, bool depthNStencil)
	//{
	//	// TODO

	//	return NULL;
	//}

	void Renderer::initializeOnce(u8 colBit, u16 w, u16 h)
	{
		// TODO verify these input parameters
		mColorBit = colBit;
		mWidth = w;
		mHeight = h;

		createManagerImpls();
		NZ_ASSERT(mDriver);
		NZ_ASSERT(mFrameBuffer);
		NZ_ASSERT(mGeometryDataManager);
		NZ_ASSERT(mShaderProgramManager);

		// TODO initialize shader libriarys.

		mMaterialManager = NZ_New MaterialManager();

		mOpaqueQueue = NZ_New RenderQueue(RenderQueue::ST_MATERIAL);
		mTransparentQueue = NZ_New RenderQueue(RenderQueue::ST_FAR2NEAR);
	}

	void Renderer::uninitialize()
	{
		NEZHA_DELETE(mOpaqueQueue)
		NEZHA_DELETE(mTransparentQueue)

		mMaterialManager->removeAll();
		NEZHA_DELETE(mMaterialManager)

		mShaderProgramManager->removeAll();
		mGeometryDataManager->releaseAllData();

		destroyManagerImpls();
	}

	NEZHA_STATIC_PROFILER_DECLARE(Renderer_beginFrame, PSI_RENDERER)
	bool Renderer::beginFrame()
	{
		NEZHA_AUTO_PROFILE(Renderer_beginFrame)

		if(!mFrameBuffer)
		{
			NEZHA_EXCEPTION(NZException::InvalidParam, "Have no valid frame buffer, can not render anything.");

			return false;
		}

		return true;
	}

	void Renderer::endFrame()
	{
		for(PArray<RenderTarget*>::iterator it = mRenderTargets.begin(); it != mRenderTargets.end(); it++)
		{
			(*it)->swapBuffers();
		}

		// main frame buffer
		mFrameBuffer->swapBuffers();

		mShaderProgramManager->update(mFrameDelta);
		mGeometryDataManager->update(mFrameDelta);
	}

	RendererParam* Renderer::createBuiltinParam(RendererParamsType rpt)
	{
		if(!mBuiltinParams[rpt])
		{
			mBuiltinParams[rpt] = NZ_New RendererParam();
			mBuiltinParams[rpt]->create(rpt);
		}

		return mBuiltinParams[rpt];
	}

	RendererParam* Renderer::getBuiltinParam(RendererParamsType rpt)
	{
		return mBuiltinParams[rpt];
	}

	u32 Renderer::differBlending(const RS_Blending& as, const RS_Blending& asRhs)
	{
		u32 differs = 0;

		if(as.mBlendingEnable)
		{
			if(!asRhs.mBlendingEnable)
			{
				++differs;
			}

			bool needSetBlendFunc = false;

			if(!asRhs.mBlendingEnable || asRhs.mSrc != as.mSrc || asRhs.mDst != as.mDst || asRhs.mSeparateBlending !=
				as.mSeparateBlending)
				needSetBlendFunc = true;

			if(!needSetBlendFunc && asRhs.mSeparateBlending && as.mSeparateBlending)
			{
				needSetBlendFunc = as.mSrcAlpha != asRhs.mSrcAlpha || as.mDstAlpha != asRhs.mDstAlpha;
			}

			if(needSetBlendFunc)
				++differs;

			bool needSetBlendEqual = false;

			if(!asRhs.mBlendingEnable || as.mOp != asRhs.mOp)
				needSetBlendEqual = true;

			if(!needSetBlendEqual && as.mSeparateBlending && !asRhs.mSeparateBlending)
				needSetBlendEqual = true;

			if(!needSetBlendEqual && asRhs.mSeparateBlending
				&& as.mAlphaOp != asRhs.mAlphaOp)
				needSetBlendEqual = true;

			if(needSetBlendEqual)
				++differs;
		}
		else
		{
			if(asRhs.mBlendingEnable)
			{
				++differs;
			}
		}

		if(as.mCmpMode != asRhs.mCmpMode)
		{
			++differs;
		}

		if(as.mCmpMode != RSCM_ALWAYS)
		{
			++differs;
		}

		return differs;
	}

	u32 Renderer::differCullOrder(const RS_CullOrder& co, const RS_CullOrder& cullRhs)
	{
		return co == cullRhs ? 0 : 1;
	}

	u32 Renderer::differDepth(const RS_Depth& rd, const RS_Depth& depthRhs)
	{
		u32 differs = 0;

		if(rd.mEnabled)
		{
			if(!depthRhs.mEnabled)
			{
				++differs;
			}

			if(!depthRhs.mEnabled || depthRhs.mCmpMode != rd.mCmpMode)
			{
				++differs;
			}
		}
		else
		{
			if(depthRhs.mEnabled)
			{
				++differs;
			}
		}

		if(rd.mWritable != depthRhs.mWritable)
		{
			++differs;
		}

		return differs;
	}

	u32 Renderer::differPolygonOffset(const RS_PolygonOffset& po, const RS_PolygonOffset& offRhs)
	{
		u32 differs = 0;

		if(po.mFillEnabled)
		{
			if(!offRhs.mFillEnabled)
			{
				++differs;
			}

			if(!offRhs.mFillEnabled || offRhs.mBias != po.mBias || offRhs.mScale != po.mScale)
			{
				++differs;
			}
		}
		else
		{
			if(offRhs.mFillEnabled)
			{
				++differs;
			}
		}

		return differs;
	}

	u32 Renderer::differStencil(const RS_Stencil& st, const RS_Stencil& stenRhs)
	{
		u32 differs = 0;

		if(st.mEnabled)
		{
			if(!stenRhs.mEnabled)
			{
				++differs;
			}

			if(st.mTwoSide && mDriver->hasCapability(GC_STENCIL_SEPARATE))
			{
				bool flip = false; // FIXME determine if texture flipping or invert vertex winding.

				if(!stenRhs.mEnabled || st.mWriteMask != stenRhs.mWriteMask)
				{
					differs += 2;
				}

				if(!stenRhs.mEnabled || st.mCmpMode != stenRhs.mCmpMode || st.mMask != stenRhs.mMask)
				{
					differs += 2;
				}

				if(!stenRhs.mEnabled || st.mOnFail != stenRhs.mOnFail || st.mOnPass != stenRhs.mOnPass || st.mOnZFail != stenRhs.mOnZFail)
				{
					differs += 2;
				}
			}
			else
			{
				if(!stenRhs.mEnabled || st.mWriteMask != stenRhs.mWriteMask)
					++differs;

				if(!stenRhs.mEnabled || st.mCmpMode != stenRhs.mCmpMode || st.mMask != stenRhs.mMask)
					++differs;

				if(!stenRhs.mEnabled || st.mOnFail != stenRhs.mOnFail || st.mOnPass != stenRhs.mOnPass || st.mOnZFail != stenRhs.mOnZFail)
					++differs;
			}
		}
		else
		{
			if(stenRhs.mEnabled)
			{
				++differs;
			}
		}

		return differs;
	}

	u32 Renderer::differFillMode(const RS_FillMode& fm, const RS_FillMode& fmRhs)
	{
		return fm == fmRhs ? 0 : 1;
	}

	u32 Renderer::differColorMask(const RS_ColorMask& cm, const RS_ColorMask& cmRhs)
	{
		u32 differs = 0;

		if(cm.mRed != cmRhs.mRed)
			++differs;

		if(cm.mGreen != cmRhs.mGreen)
			++differs;

		if(cm.mBlue != cmRhs.mBlue)
			++differs;

		if(cm.mAlpha != cmRhs.mAlpha)
			++differs;

		return differs;
	}

	void Renderer::setBlending(const RS_Blending& as)
	{
		_setBlending(as, &mCurBlendState);

		mCurBlendState = as;
	}

	void Renderer::setCullOrder(const RS_CullOrder& co)
	{
		_setCullOrder(co, &mCurCullOrder);

		mCurCullOrder = co;
	}

	void Renderer::setDepth(const RS_Depth& rd)
	{
		_setDepth(rd, &mCurDepth);

		mCurDepth = rd;
	}

	void Renderer::setPolygonOffset(const RS_PolygonOffset& po)
	{
		_setPolygonOffset(po, &mCurPolygonOffset);

		mCurPolygonOffset = po;
	}

	void Renderer::setStencil(const RS_Stencil& st)
	{
		_setStencil(st, &mCurStencil);

		mCurStencil = st;
	}

	void Renderer::setFillMode(const RS_FillMode& fm)
	{
		_setFillMode(fm, &mCurFillMode);

		mCurFillMode = fm;
	}

	void Renderer::setColorMask(const RS_ColorMask& cm)
	{
		_setColorMask(cm, &mCurColorMask);

		mCurColorMask = cm;
	}

	NEZHA_STATIC_PROFILER_DECLARE(Renderer_drawScene, PSI_RENDERER)
	void Renderer::drawScene()
	{
		NEZHA_AUTO_PROFILE(Renderer_drawScene)

		mLastRenderQueueEntry = NULL;

		restoreDefaultStates();

		// forward shading.
		for(PArray<RenderTarget*>::iterator it = mRenderTargets.begin(); it != mRenderTargets.end(); it++)
		{
			mOpaqueQueue->clear();
			mTransparentQueue->clear();
			mSceneGraph->cull((*it), mOpaqueQueue, mTransparentQueue);
			drawQueues((*it));
		}

		// main frame buffer
		mOpaqueQueue->clear();
		mTransparentQueue->clear();
		mSceneGraph->cull(mFrameBuffer, mOpaqueQueue, mTransparentQueue);
		drawQueues(mFrameBuffer);
	}

	void Renderer::drawQueues(RenderTarget* rt)
	{
		if(mCurrentCam != rt->getCamera())
		{
			mCurrentCam = rt->getCamera();

			// TODO update renderer params associated with camera.

			updateRPByCurCam(V_MATRIX);
			updateRPByCurCam(P_MATRIX);
			updateRPByCurCam(VP_MATRIX);
			updateRPByCurCam(V_MATRIX_T);
			updateRPByCurCam(V_MATRIX_I);
		}

		mOpaqueQueue->sort();
		mTransparentQueue->sort();

		u32 numVp = rt->getNumViewports();
		for(u32 i = 0; i < numVp; i++)
		{
			Viewport* vp = rt->getViewport(i);

			if(!vp->mActive)
				continue;

			setViewport(vp);
			clearFameBuffer(vp->mClearFlag, vp->mBackColor, vp->mDepthVal);
			rt->preDraw(vp);

			mOpaqueQueue->draw();
			mTransparentQueue->draw();

			rt->postDraw(vp);
		}

		if(mLastRenderQueueEntry)
		{
			mLastRenderQueueEntry->RPass->end();
		}
	}

	NEZHA_STATIC_PROFILER_DECLARE(Renderer__draw, PSI_RENDERER)
	void Renderer::_draw(RenderQueueEntry* rqe)
	{
		NEZHA_AUTO_PROFILE(Renderer__draw)

		if(mLastRenderQueueEntry == rqe)
		{
			return;
		}

		mCurrentRenderQueueEntry = rqe;
		rqe->RObject->prepareGeometry4Pipeline(mCurrentBatch);

		if(mLastRenderQueueEntry)
		{
			if(mLastRenderQueueEntry->RPass != rqe->RPass)
			{
				mLastRenderQueueEntry->RPass->end();
				mLastRenderQueueEntry->RPass->begin();
			}

			if(rqe->RPass->getProgram() != mLastRenderQueueEntry->RPass->getProgram())
			{
				setShader(rqe->RPass->getProgram());
			}

			setGeometry(&mCurrentBatch, &mLastBatch);

			setFillMode(rqe->RPass->mFillMode);
			setBlending(rqe->RPass->mBlending);
			setDepth(rqe->RPass->mDepth);
			setCullOrder(rqe->RPass->mCull);
			setPolygonOffset(rqe->RPass->mOffset);
			setStencil(rqe->RPass->mStencil);

			setShaderParameters(rqe->RPass, mLastRenderQueueEntry->RPass);

			drawSingle();
		}
		else
		{
			RenderPass* defaultPass = mDefaultMaterial->_getPass(0);

			rqe->RPass->begin();

			// no shader, set directly
			setShader(rqe->RPass->getProgram());
			
			setGeometry(&mCurrentBatch, NULL);

			setFillMode(rqe->RPass->mFillMode);
			setBlending(rqe->RPass->mBlending);
			setDepth(rqe->RPass->mDepth);
			setCullOrder(rqe->RPass->mCull);
			setPolygonOffset(rqe->RPass->mOffset);
			setStencil(rqe->RPass->mStencil);

			setShaderParameters(rqe->RPass);

			drawSingle();
		}

		mLastRenderQueueEntry = rqe;
		mLastBatch = mCurrentBatch;
	}

	void Renderer::drawSingle()
	{
		// stats
	}

	void Renderer::setShaderParameters(RenderPass* pass, RenderPass* prevPass)
	{
		u32 numParams = pass->getNumParams();
		u32 numPreParams = prevPass ? prevPass->getNumParams() : 0;
		for(u32 i = 0; i < numParams;i ++)
		{
			ShaderParameter* sp = pass->getParam(i);

			if(sp->getCategory() == SPC_BUILTIN)
			{
				updateRendererParams(static_cast<RendererParam*>(sp));
			}

			setShaderParameterImpl(sp, pass);
		}
	}

	void Renderer::restoreDefaultStates()
	{
		if(mDefBlendStateOverride)
		{
			_setBlending(mDefBlendState);
			mCurBlendState = mDefBlendState;
		}

		if(mDefCullOrderOverride)
		{
			_setCullOrder(mDefCullOrder);
			mCurCullOrder = mDefCullOrder;
		}

		if(mDefDepthOverride)
		{
			_setDepth(mDefDepth);
			mCurDepth = mDefDepth;
		}

		if(mDefOffsetOverride)
		{
			_setPolygonOffset(mDefPolygonOffset);
			mCurPolygonOffset = mDefPolygonOffset;
		}

		if(mDefFillModeOverride)
		{
			_setFillMode(mDefFillMode);
			mCurFillMode = mDefFillMode;
		}

		if(mDefStencilOverride)
		{
			_setStencil(mDefStencil);
			mCurStencil = mDefStencil;
		}

		if(mDefColorMaskOverride)
		{
			_setColorMask(mDefColorMask);
			mCurColorMask = mDefColorMask;
		}
	}

	void Renderer::updateRPByCurCam(RendererParamsType rpt)
	{
		if(!mBuiltinParams[rpt])
			return;

		switch(rpt)
		{
			// basic matrices
		case V_MATRIX:
			mBuiltinParams[rpt]->setData(mCurrentCam->getViewMat());
			break;
		case P_MATRIX:
			mBuiltinParams[rpt]->setData(mCurrentCam->getProjectionMat());
			break;
		case WV_MATRIX:
			break;
		case VP_MATRIX:
			{
				Matrix4x4& v = *(mBuiltinParams[rpt]->getData<Matrix4x4>());
				v = mCurrentCam->getViewMat();
				v = v * mCurrentCam->getProjectionMat();
			}
			break;
		case WVP_MATRIX:
			break;

			// transpose
		case W_MATRIX_T:
			break;
		case V_MATRIX_T:
			{
				mBuiltinParams[rpt]->setData(mCurrentCam->getViewMat().transpose());
			}
			break;
		case P_MATRIX_T:
			break;
		case WV_MATRIX_T:
			break;
		case VP_MATRIX_T:
			break;
		case WVP_MATRIX_T:
			break;

			// inverse
		case W_MATRIX_I:
			break;
		case V_MATRIX_I:
			mBuiltinParams[rpt]->setData(mCurrentCam->getViewMat().inverse());
			break;
		case P_MATRIX_I:
			break;
		case WV_MATRIX_I:
			break;
		case VP_MATRIX_I:
			break;
		case WVP_MATRIX_I:
			break;

			// inverse transpose
		case W_MATRIX_I_T:
			break;
		case V_MATRIX_I_T:
			break;
		case P_MATRIX_I_T:
			break;
		case WV_MATRIX_I_T:
			break;
		case VP_MATRIX_I_T:
			break;
		case WVP_MATRIX_I_T:
			break;
		}
	}

	void Renderer::updateRendererParams(RendererParam* rp)
	{
		switch(rp->getRendererParamType())
		{
			// basic matrices
		case W_MATRIX:
			if(mCurrentRenderQueueEntry->RObject->getSpatial())
			{
				rp->setData(mCurrentRenderQueueEntry->RObject->getSpatial()->getWorldTransform());
			}
			break;
		case V_MATRIX:
			break;
		case P_MATRIX:
			break;
		case WV_MATRIX:
			if(mCurrentRenderQueueEntry->RObject->getSpatial())
			{
				Matrix4x4& w = *(rp->getData<Matrix4x4>());
				w = mCurrentRenderQueueEntry->RObject->getSpatial()->getWorldTransform();
				w = w * mCurrentCam->getViewMat();
			}
			break;
		case VP_MATRIX:
			break;
		case WVP_MATRIX:
			if(mCurrentRenderQueueEntry->RObject->getSpatial())
			{
				Matrix4x4& w = *(rp->getData<Matrix4x4>());
				w = mCurrentRenderQueueEntry->RObject->getSpatial()->getWorldTransform();
				w = w * mCurrentCam->getViewMat();
				w = w * mCurrentCam->getProjectionMat();
			}
			break;

			// transpose
		case W_MATRIX_T:
			break;
		case V_MATRIX_T:
			break;
		case P_MATRIX_T:
			break;
		case WV_MATRIX_T:
			break;
		case VP_MATRIX_T:
			break;
		case WVP_MATRIX_T:
			break;

			// inverse
		case W_MATRIX_I:
			break;
		case V_MATRIX_I:
			break;
		case P_MATRIX_I:
			break;
		case WV_MATRIX_I:
			break;
		case VP_MATRIX_I:
			break;
		case WVP_MATRIX_I:
			break;

			// inverse transpose
		case W_MATRIX_I_T:
			break;
		case V_MATRIX_I_T:
			break;
		case P_MATRIX_I_T:
			break;
		case WV_MATRIX_I_T:
			break;
		case VP_MATRIX_I_T:
			break;
		case WVP_MATRIX_I_T:
			break;
		}
	}

}